Анализ оттока клиентов банка «Метанпромбанк»¶
Нам предоставлены данные заказчиком в лице менеджмента банка «Метанпромбанк». Наша задача проанализировать клиентов банка и выделить сегменты клиентов, которые склонны уходить из банка, для дальнейшей работы и принятия решений на основе этой информации и наших выводов менеджментом.
Загрузка данных и библиотек¶
# установим phik
! pip install phik
Collecting phik
Downloading phik-0.12.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (686 kB)
|████████████████████████████████| 686 kB 1.3 MB/s eta 0:00:01
Requirement already satisfied: matplotlib>=2.2.3 in /opt/conda/lib/python3.9/site-packages (from phik) (3.3.4)
Requirement already satisfied: scipy>=1.5.2 in /opt/conda/lib/python3.9/site-packages (from phik) (1.9.1)
Requirement already satisfied: pandas>=0.25.1 in /opt/conda/lib/python3.9/site-packages (from phik) (1.2.4)
Requirement already satisfied: joblib>=0.14.1 in /opt/conda/lib/python3.9/site-packages (from phik) (1.1.0)
Requirement already satisfied: numpy>=1.18.0 in /opt/conda/lib/python3.9/site-packages (from phik) (1.21.1)
Requirement already satisfied: python-dateutil>=2.1 in /opt/conda/lib/python3.9/site-packages (from matplotlib>=2.2.3->phik) (2.8.1)
Requirement already satisfied: pillow>=6.2.0 in /opt/conda/lib/python3.9/site-packages (from matplotlib>=2.2.3->phik) (8.4.0)
Requirement already satisfied: kiwisolver>=1.0.1 in /opt/conda/lib/python3.9/site-packages (from matplotlib>=2.2.3->phik) (1.4.4)
Requirement already satisfied: pyparsing!=2.0.4,!=2.1.2,!=2.1.6,>=2.0.3 in /opt/conda/lib/python3.9/site-packages (from matplotlib>=2.2.3->phik) (2.4.7)
Requirement already satisfied: cycler>=0.10 in /opt/conda/lib/python3.9/site-packages (from matplotlib>=2.2.3->phik) (0.11.0)
Requirement already satisfied: pytz>=2017.3 in /opt/conda/lib/python3.9/site-packages (from pandas>=0.25.1->phik) (2021.1)
Requirement already satisfied: six>=1.5 in /opt/conda/lib/python3.9/site-packages (from python-dateutil>=2.1->matplotlib>=2.2.3->phik) (1.16.0)
Installing collected packages: phik
Successfully installed phik-0.12.4
#загрузим библиотеки
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import math
import scipy.stats as stats
import warnings
from plotly import graph_objects as go
from scipy import stats as st
import plotly.express as px
import warnings; warnings.filterwarnings(action = 'ignore')
import phik
from phik import resources, report
#загрузим предоставленные заказчиком данные
data = pd.read_csv('https://code.s3.yandex.net/datasets/bank_scrooge.csv')
data.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 10000 entries, 0 to 9999 Data columns (total 12 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 USERID 10000 non-null int64 1 score 10000 non-null float64 2 city 10000 non-null object 3 gender 10000 non-null object 4 age 9974 non-null float64 5 equity 10000 non-null int64 6 balance 7705 non-null float64 7 products 10000 non-null int64 8 credit_card 10000 non-null int64 9 last_activity 10000 non-null int64 10 EST_SALARY 10000 non-null float64 11 churn 10000 non-null int64 dtypes: float64(4), int64(6), object(2) memory usage: 937.6+ KB
При первом рассмотрении мы видим, что нам потребуется перевести названия к общему виду. Также мы видим, что в полях возраста, и баланса отсутствует часть данных, балансовые данные отсутствуют почти в 25% строк, что делает их "сброс" невозможным, а выводить баланс по среднему или медиане будет неверным решением, поэтому на данные по балансу мы не будем никак воздействовать, а вот данные по возрасты мы изучим и, возможно, сбросим.
#переименуем столбцы для удобства дальнейшей работы
data.rename(columns={"USERID": "user_id", "EST_SALARY": "est_salary"}, inplace=True)
Изучение данных¶
Изучение общей информации по столбцам¶
display(data.head(5))
| user_id | score | city | gender | age | equity | balance | products | credit_card | last_activity | est_salary | churn | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 183012 | 850.0 | Рыбинск | Ж | 25.0 | 1 | 59214.82 | 2 | 0 | 1 | 75719.14 | 1 |
| 1 | 146556 | 861.0 | Рыбинск | Ж | 37.0 | 5 | 850594.33 | 3 | 1 | 0 | 86621.77 | 0 |
| 2 | 120722 | 892.0 | Рыбинск | Ж | 30.0 | 0 | NaN | 1 | 1 | 1 | 107683.34 | 0 |
| 3 | 225363 | 866.0 | Ярославль | Ж | 51.0 | 5 | 1524746.26 | 2 | 0 | 1 | 174423.53 | 1 |
| 4 | 157978 | 730.0 | Ярославль | М | 34.0 | 5 | 174.00 | 1 | 1 | 0 | 67353.16 | 1 |
Опишем имеющиеся у нас столбцы:
user_id- уникальный идентификатор пользователя банкаscore- баллы кредитного скоринга (больше = выше, лучше)city- городgender- пол (М/Ж)age- возрастequity- количество баллов собственностиbalance- балансproducts- количество банковских продуктовcredit_card- есть ли кредитная картаlast_activity- активный ли клиентest_salary- оценочный доход клиентаchurn- признак оттока(уходит/не уходит)
#повторно выведем информацию о столбцах
data.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 10000 entries, 0 to 9999 Data columns (total 12 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 user_id 10000 non-null int64 1 score 10000 non-null float64 2 city 10000 non-null object 3 gender 10000 non-null object 4 age 9974 non-null float64 5 equity 10000 non-null int64 6 balance 7705 non-null float64 7 products 10000 non-null int64 8 credit_card 10000 non-null int64 9 last_activity 10000 non-null int64 10 est_salary 10000 non-null float64 11 churn 10000 non-null int64 dtypes: float64(4), int64(6), object(2) memory usage: 937.6+ KB
#изучим возраст
data['age'].describe()
count 9974.000000 mean 42.734409 std 12.179971 min 18.000000 25% 33.000000 50% 40.000000 75% 51.000000 max 86.000000 Name: age, dtype: float64
Поиск дубликатов¶
data.duplicated().sum()
0
# прямых дубликатов не обнаружили, проверим по городу и ID
data.duplicated(subset=['user_id', 'city']).sum()
0
# прямых дубликатов также нет, тогда посмотрим на дубликаты по ID
data.duplicated(subset=['user_id']).sum()
73
#изучим их внимательнее
display(data[data.duplicated(subset=['user_id'], keep=False)].sort_values(by='user_id'))
| user_id | score | city | gender | age | equity | balance | products | credit_card | last_activity | est_salary | churn | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1893 | 116540 | 883.0 | Рыбинск | Ж | 55.0 | 1 | 362756.49 | 3 | 0 | 1 | 175920.48 | 1 |
| 7694 | 116540 | 887.0 | Ярославль | Ж | 38.0 | 0 | NaN | 1 | 0 | 1 | 119247.61 | 0 |
| 7542 | 117943 | 880.0 | Ярославль | Ж | 40.0 | 0 | NaN | 1 | 1 | 0 | 137718.93 | 0 |
| 4866 | 117943 | 855.0 | Рыбинск | Ж | 32.0 | 6 | 1036832.93 | 4 | 1 | 1 | 107792.71 | 1 |
| 5896 | 120258 | 905.0 | Ярославль | М | 30.0 | 0 | NaN | 1 | 1 | 1 | 146427.96 | 0 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 2597 | 226719 | 990.0 | Ярославль | М | 37.0 | 4 | 14648692.14 | 2 | 0 | 0 | 934412.61 | 1 |
| 8205 | 227795 | 840.0 | Рыбинск | М | 34.0 | 2 | 350768.03 | 1 | 1 | 0 | 102036.14 | 1 |
| 8497 | 227795 | 839.0 | Ярославль | М | 34.0 | 2 | 326593.14 | 2 | 1 | 0 | 103314.92 | 0 |
| 6457 | 228075 | 839.0 | Рыбинск | М | 39.0 | 5 | 507199.85 | 3 | 0 | 1 | 85195.80 | 0 |
| 1247 | 228075 | 932.0 | Ярославль | М | NaN | 5 | 7601719.20 | 2 | 1 | 1 | 408121.16 | 0 |
146 rows × 12 columns
Вывод из поиска дубликатов:
Дубликаты среду уникальных идентификаторов, конечно, есть, но их ничтожно мало (менее 1%), а также мы видим на представленных данных, что дубликаты эти - это одни и те же пользователи услуг банка, но с продуктами банка из разных городов, думаю, что мы можем оставить эти данные, как они есть, чтобы учитывать их в расчётах.
Изучение пустот¶
Из общих данных по столбцам мы видим, что данные отсутствуют в двух столбца age и balance.
balance отсутствует почти у 25% данных, что делает его удаление из данных невозможным, слишком большой других данных о пользователях мы потеряем, а вот age не так много, давайте его изучим.
Изучение столбца age¶
#сколько точно клиентов без возраста в базе
data[data['age'].isnull()].count()
user_id 26 score 26 city 26 gender 26 age 0 equity 26 balance 10 products 26 credit_card 26 last_activity 26 est_salary 26 churn 26 dtype: int64
#посмотрим на них внимательнее
data[data['age'].isnull()]
| user_id | score | city | gender | age | equity | balance | products | credit_card | last_activity | est_salary | churn | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1247 | 228075 | 932.0 | Ярославль | М | NaN | 5 | 7601719.20 | 2 | 1 | 1 | 408121.16 | 0 |
| 2165 | 187635 | 692.0 | Рыбинск | Ж | NaN | 0 | NaN | 1 | 1 | 1 | 160368.82 | 0 |
| 2444 | 221156 | 913.0 | Ярославль | М | NaN | 0 | NaN | 1 | 1 | 1 | 135693.24 | 0 |
| 3091 | 138660 | 836.0 | Ростов | Ж | NaN | 5 | 294315.53 | 2 | 0 | 1 | 63310.22 | 1 |
| 4912 | 210674 | 834.0 | Рыбинск | М | NaN | 1 | 238330.52 | 2 | 0 | 1 | 93775.06 | 0 |
| 5470 | 218868 | 827.0 | Рыбинск | Ж | NaN | 4 | 448959.07 | 2 | 1 | 1 | 67835.95 | 0 |
| 5495 | 151662 | 884.0 | Рыбинск | Ж | NaN | 0 | NaN | 1 | 1 | 1 | 137500.77 | 0 |
| 7236 | 210135 | 908.0 | Рыбинск | Ж | NaN | 4 | 1120340.31 | 3 | 1 | 1 | 85002.15 | 0 |
| 7248 | 219343 | 920.0 | Рыбинск | Ж | NaN | 0 | NaN | 1 | 1 | 0 | 159248.67 | 0 |
| 7345 | 184913 | 829.0 | Ярославль | Ж | NaN | 3 | 188648.77 | 2 | 0 | 1 | 75206.90 | 0 |
| 7409 | 214031 | 777.0 | Ярославль | М | NaN | 2 | 171510.23 | 1 | 1 | 1 | 75409.63 | 0 |
| 8015 | 198635 | 670.0 | Ярославль | Ж | NaN | 0 | NaN | 1 | 1 | 1 | 168699.33 | 0 |
| 8070 | 226550 | 940.0 | Рыбинск | М | NaN | 0 | NaN | 1 | 0 | 1 | 147696.95 | 0 |
| 8293 | 216848 | 930.0 | Ярославль | М | NaN | 0 | NaN | 1 | 1 | 1 | 199542.51 | 0 |
| 8385 | 206759 | 915.0 | Рыбинск | М | NaN | 0 | NaN | 1 | 1 | 0 | 71179.53 | 0 |
| 8449 | 210898 | 805.0 | Ярославль | Ж | NaN | 0 | NaN | 1 | 0 | 1 | 922080.25 | 0 |
| 8632 | 221197 | 893.0 | Ярославль | М | NaN | 0 | NaN | 1 | 1 | 0 | 173929.92 | 0 |
| 8785 | 127440 | 663.0 | Ярославль | М | NaN | 0 | NaN | 1 | 1 | 1 | 117197.56 | 0 |
| 9104 | 222480 | 776.0 | Рыбинск | Ж | NaN | 5 | 796735.09 | 1 | 1 | 1 | 55073.63 | 0 |
| 9301 | 202983 | 942.0 | Рыбинск | Ж | NaN | 0 | NaN | 1 | 1 | 1 | 163804.73 | 0 |
| 9380 | 187459 | 894.0 | Рыбинск | М | NaN | 0 | NaN | 1 | 1 | 0 | 178012.28 | 0 |
| 9457 | 141945 | 929.0 | Ярославль | М | NaN | 0 | NaN | 1 | 1 | 0 | 381868.89 | 0 |
| 9632 | 185829 | 927.0 | Ярославль | М | NaN | 0 | NaN | 1 | 1 | 0 | 231254.86 | 0 |
| 9634 | 221809 | 917.0 | Ярославль | М | NaN | 0 | NaN | 1 | 1 | 1 | 192644.15 | 0 |
| 9667 | 163657 | 849.0 | Ярославль | М | NaN | 4 | 1254013.85 | 2 | 1 | 1 | 119106.67 | 0 |
| 9819 | 140934 | 832.0 | Рыбинск | Ж | NaN | 3 | 385763.16 | 2 | 0 | 1 | 59651.35 | 0 |
# изучим балансы среди тех клиентов, что у нас без возраста
print(data[data['age'].isnull()]['balance'].sum())
data[data['age'].isnull()]['balance'].sum()/data[data['age'].isnull()]['balance'].count()
12500335.73
1250033.573
# изучим их совокупный и средний доход
print(data[data['age'].isnull()]['est_salary'].sum())
data[data['age'].isnull()]['est_salary'].sum()/data[data['age'].isnull()]['est_salary'].count()
4643215.18
178585.1992307692
Вывод из поиска пустых значений:
Судя, по всему, наши 26 людей без возрастов довольно обеспеченные клиенты, ведь их расчетный средний доход 175к рублей, а баланс среди тех, кто указан около 1,25кк рублей, оставим эти строки, чтобы не упустить эти данные в будущем анализе. А также немаловажно отметить, что среди них всего 1 клиент, который планирует уходить, остальные держат деньги на счетаъ и/или могут в любой момент вернуться в банк, что интересно для нас в силу их ежемесячного заработка.
Вывод из изучения данных:
Данные готовы к использованию, дальнейшая их предобработка не потребуется, названия мы перевели в начале этой главы, чтобы было удобнее работать, age не можем снести из-за больших объёмов денег этих клиентов, а balance из-за слишком большого объёма данных
EDA¶
Изучение данных по столбцам, их значений и выбросов¶
#быстро визуализироваем имеющиеся данные и их столбцы
data.hist(figsize=(10,10));
Изучение городов¶
#изучим города в данных
data['city'].unique()
array(['Рыбинск', 'Ярославль', 'Ростов'], dtype=object)
data.groupby('city')['user_id'].count().reset_index()
| city | user_id | |
|---|---|---|
| 0 | Ростов | 1417 |
| 1 | Рыбинск | 2695 |
| 2 | Ярославль | 5888 |
В наших данных представлены 3 города: Ростов, Рыбинск и Ярославль. Большая часть клиентов приходится на Ярославль, на втором месте Рыбинск и замыкает эту тройку Ростов.
Изучение возраста¶
print(data['age'].nunique())
data['age'].describe()
68
count 9974.000000 mean 42.734409 std 12.179971 min 18.000000 25% 33.000000 50% 40.000000 75% 51.000000 max 86.000000 Name: age, dtype: float64
data['age'].hist(figsize=(15,5));
Изучив показатели по возрасту клиентов банка, мы видим, что большинство наших пользователей приходится на 25-45лет.
Изучение баланса¶
# для удобства считывания данных введём новый столбец с показателем баланса в млн. рублей
data['balance_mln'] = data['balance']/1000000
# изучим полученные данные
display(data['balance_mln'].describe())
data['balance_mln'].hist(figsize=(15,5));
count 7705.000000 mean 0.827794 std 1.980614 min 0.000000 25% 0.295554 50% 0.524272 75% 0.980706 max 119.113552 Name: balance_mln, dtype: float64
Вывод из изучения данных о балансе:
Среднее значение баланка приходится приблизительно на 828т.р., квартильные значения считаются между 295к и 980к рублей.
Стоит отметить, что у одного из пользоваетелей банка есть на балансе более 119млн. рублей.
Изучение количества продуктов¶
# изучим количество клиентов банка по количеству продуктов на клиента
display(data.groupby('products')['user_id'].count().reset_index().sort_values(by='user_id'))
| products | user_id | |
|---|---|---|
| 0 | 0 | 1 |
| 5 | 5 | 19 |
| 4 | 4 | 474 |
| 3 | 3 | 1039 |
| 1 | 1 | 3341 |
| 2 | 2 | 5126 |
#смотрим на пользователя без банковских продуктов
display(data[data['products']==0])
| user_id | score | city | gender | age | equity | balance | products | credit_card | last_activity | est_salary | churn | balance_mln | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 8957 | 147837 | 962.0 | Рыбинск | Ж | 79.0 | 3 | NaN | 0 | 0 | 0 | 25063.96 | 1 | NaN |
# убираем пользователя из данных
data = data[data['products']!=0]
display(data[data['products']==0])
| user_id | score | city | gender | age | equity | balance | products | credit_card | last_activity | est_salary | churn | balance_mln |
|---|
Вывод из изучения данных по количеству продуктов:
Большинство пользователей имеют 2 банковских продукта, реже 1, и около 1500 клиентов имеют 3 и более продукта, кстати, среди данных был пользователь без продуктов, его мы удалим, т.к. там не самый интересный для нас клиент и на основе предыдущих критериев, так ещё и без продуктов банка совсем.
Изучение данных скоринга¶
display(data['score'].describe())
data['score'].hist(figsize=(15,5));
count 9999.000000 mean 848.688069 std 65.441981 min 642.000000 25% 802.000000 50% 853.000000 75% 900.000000 max 1000.000000 Name: score, dtype: float64
Выводы из изучения скоринга клиентов:
Среднее значение скоринга около 850, межквартильный размах от Q1 до Q3 скоринга между 802 и 900.
Изучение количества клиентов по полу¶
# изучим количество клиентов банка по полу
display(data.groupby('gender')['user_id'].count().reset_index().sort_values(by='user_id'))
fig = go.Figure(data=[go.Pie(labels=data['gender'], values=data['user_id'])])
fig.show()
| gender | user_id | |
|---|---|---|
| 0 | Ж | 4994 |
| 1 | М | 5005 |
Выводы из изучения пола клиентов:
Количество клиентов в датасете распределено по полу практически поровну, с минимальным отклонением в сторону мужской половины.
Изучение количества клиентов по наличию кредитной карты¶
# изучим количество клиентов банка по полу
display(data.groupby('credit_card')['user_id'].count().reset_index().sort_values(by='user_id'))
fig = go.Figure(data=[go.Pie(labels=data['credit_card'], values=data['user_id'])])
fig.show()
| credit_card | user_id | |
|---|---|---|
| 0 | 0 | 3195 |
| 1 | 1 | 6804 |
Выводы из изучения наличия кредитного продукта среди клиентов:
Количество клиентов в датасете по кредитному продукту распределено приблизительно, как 2/3 к 1/3, а именно, более 68% клиентов являются пользователями кредитных продуктов.
Влияние показателей на отток¶
print('Пользователей уходит(%): ',round(data[data['churn'] == 1]['user_id'].count() / data['user_id'].count() * 100,2))
#Изучим средние значения показателей по двум группам (остающихся и уходящих)
churn_rate = data.groupby('churn').agg('mean').T
churn_rate['perc_diff'] = round(((churn_rate[1] - churn_rate[0]) / churn_rate[0]) * 100,1)
display(churn_rate)
Пользователей уходит(%): 18.19
| churn | 0 | 1 | perc_diff |
|---|---|---|---|
| user_id | 172004.359046 | 1.709751e+05 | -0.6 |
| score | 845.428362 | 8.633469e+02 | 2.1 |
| age | 43.020846 | 4.142959e+01 | -3.7 |
| equity | 2.374817 | 3.764156e+00 | 58.5 |
| balance | 733982.585648 | 1.133993e+06 | 54.5 |
| products | 1.757579 | 2.377130e+00 | 35.3 |
| credit_card | 0.709169 | 5.514019e-01 | -22.2 |
| last_activity | 0.483741 | 7.025838e-01 | 45.2 |
| est_salary | 147783.200108 | 1.483107e+05 | 0.4 |
| balance_mln | 0.733983 | 1.133993e+00 | 54.5 |
Рассмотрим каждый показатель в отдельности:
Скоринг- отличается незначительно, всего около 2%Возраст- отличается незначительно, около 4%Собственность- значительно отличается, разница почти 60% между группамиБаланс(млн.)- значительно отличается, разница почти 55% между группамиКоличество продуктов- значительно отличается, разница около 35% между группамиНаличие кредитного продукта- значительно отличается, разница около 22% между группамиАктивность- значительно отличается, разница около 45% между группамиРасчетный доход-отличается незначительно, всего около 0,5%
Итог из таблицы:
Главными критериями различия между группами являются количество используемых банковских продуктов, баланс на счету, рейтинг собственности и их активность, а именно - уходящие клиенты менее активны, имеют значительно меньший баланс,
# функция для построения гистограмм
def def_plots(col, title,bins=15):
fig, ax = plt.subplots(figsize=(15, 5))
sns.histplot(data=data,
hue='churn',
x=col,
stat='density',
common_norm=False,
palette='Pastel2',
bins=bins,
ax=ax)
ax.set_xlabel(col)
ax.set_ylabel('Распределение клиентов')
ax.set_title(title);
def_plots('score','Распределение скоринга среди ушедших и оставшихся пользователей')
fig, ax = plt.subplots(figsize=(15, 5))
plt.xlim(0,3)
sns.histplot(data=data,
hue='churn',
x='balance_mln',
stat='density',
common_norm=False,
palette='Pastel2',
bins=1500,
ax=ax)
ax.set_xlabel('Балланс, млн.руб.')
ax.set_ylabel('Распределение клиентов')
ax.set_title('Распределение баланса среди ушедших и оставшихся пользователей');
def_plots('age','Распределение возраста среди ушедших и оставшихся пользователей',bins=50)
def_plots('equity','Распределение баллов недвижимости среди ушедших и оставшихся пользователей',bins=9)
def_plots('products','Распределение количества продуктов среди ушедших и оставшихся пользователей',bins=5)
def_plots('credit_card','Распределение наличия кредитной карты среди ушедших и оставшихся пользователей',bins=2)
def_plots('last_activity','Распределение активности среди ушедших и оставшихся пользователей',bins=3)
def_plots('gender','Распределение пола среди ушедших и оставшихся пользователей',bins=2)
def_plots('city','Распределение города клиента среди ушедших и оставшихся пользователей',bins=3)
def_plots('est_salary','Распределение предполагаемого дохода клиента среди ушедших и оставшихся пользователей',bins=30)
Вывод из исследования оттока:
- Видна зависимость скоринга на отток клиентов (в районе 820-900 видим явный отток)
- Чем больше денег на счету - тем вероятнее клиент уходит (видим отток, начиная от 700к и далее)
- Чаще отток происходит у клиентов в возрасте от 25 до 35, а также между 50 и 60 годами.
- Наличие 3 и более баллов недвижимости также признак оттока
- Наличие более 2 продуктов прямо влияет на отток (видим, что пользователи с 2 и более продуктами наиболее подвержены отточности)
- Наличие кредитного продукта положительно влияет на усидчивость клиентов
- Активные клиенты уходят чаще, чем клиенты, которые давно не проявляли активность
- Мужчины уходят чаще женщин
- Чаще клиенты уходят в городе Ярославль
- Клиенты с заработком более 100т.р. уходят чаще, чем клиенты с меньшим доходом.
Корреляция показателей¶
# построим хитмап для оценки корреляции показателей
fig, ax = plt.subplots(figsize=(14, 8))
data_corr = data.drop('user_id', axis=1)
corr_matrix = data_corr.phik_matrix()
sns.heatmap(corr_matrix, annot=True, ax=ax, cmap='Reds', fmt='.1%', alpha=0.8)
plt.show()
interval columns not set, guessing: ['score', 'age', 'equity', 'balance', 'products', 'credit_card', 'last_activity', 'est_salary', 'churn', 'balance_mln']
Выводы:
- Больше остальных с целевой переменной коррелирует показатель баллов недвижимости (equity) - чем их больше, тем выше показатель оттока.
- Следующим параметром по значимости коррелирует активность пользователей (activity) - чем пользователи активнее, тем выше показатель оттока.
- Далее по значимости следует показатель количества баллов собственности (equity) - чем их больше, тем выше показатель оттока.
В целом матрица соответствует ранее сделанным выводам.
Сегментация пользователей по параметру оттока¶
# вспомним какой у нас средний показатель оттока
print('В среднем, согласно данным, уходит(%): ',round(data[data['churn'] == 1]['user_id'].count() / data['user_id'].count() * 100,0))
В среднем, согласно данным, уходит(%): 18.0
На основе прошлых данных соберём несколько сегментов и найдём те, которые дают наибольший отток
# 1 сегмент: мужчины с балансом выше 700к
segment_1 = data.query("gender == 'М' and balance_mln >= 0.7")['churn'].agg({'count','sum'}).round(2)
segment_1['%'] = round(segment_1['sum']/segment_1['count']*100,0)
print(segment_1)
sum 647.0 count 1461.0 % 44.0 Name: churn, dtype: float64
1 рассматриваемый сегмент это мужчины и балансом от 700т.р., мы видим, что таких клиентов в нашей базе почти 1500, и из них отток почти у 44%.
# 2 сегмент: мужчины с 2 и более продуктами и балансом более 700к
segment_2 = data.query("gender == 'М' and products >= 2 and balance_mln >= 0.7")['churn'].agg({'count','sum'}).round(2)
segment_2['%'] = round(segment_2['sum']/segment_2['count']*100,0)
print(segment_2)
sum 596.0 count 1260.0 % 47.0 Name: churn, dtype: float64
2 сегмент это клиенты-мужчины с 2 и более продуктами и балансом от 700т.р., таких клиентов в нашей базе около 1200, и из них отток у 47%.
# 3 сегмент: мужчины с 2 и более продуктами в возрасте до 35 лет
segment_3 = data.query("gender == 'М' and products >= 2 and age <= 35")['churn'].agg({'count','sum'}).round(2)
segment_3['%'] = round(segment_3['sum']/segment_3['count']*100,0)
print(segment_3)
sum 407.0 count 1218.0 % 33.0 Name: churn, dtype: float64
3 сегмент представлен мужчинами с 2 и более продуктами в возрасте до 35 лет(вкл.), в нашей базе больше 1200 таких клиентов, а отток у таких клиентов 33%.
# 4 сегмент: клиенты в возрасте от 18 до 30 и 2 и более продуктами
segment_4 = data.query("products >= 2 and age >= 18 and age <= 30")['churn'].agg({'count','sum'}).round(2)
segment_4['%'] = round(segment_4['sum']/segment_4['count']*100,0)
print(segment_4)
sum 276.0 count 949.0 % 29.0 Name: churn, dtype: float64
4 рассматриваемый сегмент один из самых, на мой взгляд, перспективных. Это клиенты в возраст от 18 до 30 лет, которые уже используют 2 и более продуктов банка. В базе почти 1000 подобных клиентов и отток у них 29%.
# 5 сегмент: мужчины в возрасте от 50 лет
segment_5 = data.query("gender == 'М' and age >= 50")['churn'].agg({'count','sum'}).round(2)
segment_5['%'] = round(segment_5['sum']/segment_5['count']*100,0)
print(segment_5)
sum 297.0 count 1000.0 % 30.0 Name: churn, dtype: float64
Наконец, 5 довольно отточный сегмент - это мужчины от 50 лет, которые при численности в 1000 клиентов в базе показывают отток в 297 клиентов(около 30%)
Проверка гипотез¶
Гипотеза №1¶
Первая гипотеза звучит как: различается ли значимо доход между теми клиентами, что ушли и теми, что остались.
Нулевая гипотеза (H0):
Доход между клиентами, которые ушли, и действующими клиентами, не различается значимо
Альтернативная гипотеза (H1):
Доход между клиентами, которые ушли, и действующими клиентами, различается значимо
alpha = .05 # критический уровень статистической значимости
results = st.stats.ttest_ind(data[data['churn'] == 0]['est_salary'],
data[data['churn'] == 1]['est_salary'])
print('p-значение: ', results.pvalue) # тест двухсторонний
if results.pvalue < alpha:
print("Отвергаем нулевую гипотезу")
else:
print("Не получилось отвергнуть нулевую гипотезу")
p-значение: 0.8839364433181659 Не получилось отвергнуть нулевую гипотезу
По результатам теста мы видим, что не удалось опровергнуть гипотезу о различии дохода между теми клиентами, которые уходят и теми, которые остаются.
Гипотеза №2¶
Вторая гипотеза звучит как: различается ли значимо баланс между теми клиентами, что ушли и теми, что остались.
Нулевая гипотеза (H0):
Баланс между клиентами, которые ушли, и действующими клиентами, не различается значимо
Альтернативная гипотеза (H1):
Баланс между клиентами, которые ушли, и действующими клиентами, различается значимо
alpha = 0.05 # критический уровень статистической значимости
group1 = data[data['churn'] == 0]['balance'].dropna() # Значения 'balance' для churn=0
group2 = data[data['churn'] == 1]['balance'].dropna() # Значения 'balance' для churn=1
results = st.ttest_ind(group1, group2)
print('p-значение:', results.pvalue) # тест двухсторонний
if results.pvalue < alpha:
print("Отвергаем нулевую гипотезу")
else:
print("Не получилось отвергнуть нулевую гипотезу")
p-значение: 5.2961006573922e-14 Отвергаем нулевую гипотезу
По результатам теста гипотезы мы видим, что нулевую гипотезу об отсутствии различия в балансе между теми клиентами, которые уходят и теми, которые остаются, отвергаем.
Выводы и рекомендации¶
Мы исследовали базу, предоставленную нам заказчиком, после чего определели критерии отточности и по ним выстроили 5 сегментов клиентов, по которым ниже также дадим комментарии, после чего провели проверку двух гипотез, вероятно, влияющих на отточность клиентов.
В первую очередь стоит отметить параметры по которым видна явная отточность клиентов:
- Видна зависимость скоринга на отток клиентов (в районе 820-900 видим явный отток)
- Чем больше денег на счету - тем вероятнее клиент уходит (видим отток, начиная от 700к и далее)
- Чаще отток происходит у клиентов в возрасте от 25 до 35, а также между 50 и 60 годами.
- Наличие 3 и более баллов недвижимости также признак оттока
- Наличие более 2 продуктов прямо влияет на отток (видим, что пользователи с 2 и более продуктами наиболее подвержены отточности)
- Наличие кредитного продукта положительно влияет на усидчивость клиентов
- Активные клиенты уходят чаще, чем клиенты, которые давно не проявляли активность
- Мужчины уходят чаще женщин
- Чаще клиенты уходят в городе Ярославль
- Клиенты с заработком более 100т.р. уходят чаще, чем клиенты с меньшим доходом.
Выделенные сегменты и рекомендации по ним:
1 сегмент - мужчины с балансом более 700т.р.(в базе 1461 клиент подходящий образу, из них 647 клиентов уходит/ушло(44%))
На основе этого сегмента стоит рассмотреть программы мотивации, кэшбека и др. маркетинговые продукты для удержания группы, например, повышенный кэшбек на инструменты и заправки, а учитывая их баланс, возможно, предоставление специальных условий для клиентов с крупными депозитами, таких как повышенные процентные ставки по депозитам или индивидуальные инвестиционные консультации.
2 сегмент - мужчины с 2 и более продуктами и аналогичным балансом, которых не сильно меньше(1260, но при этом параметр отточности уже 47%). Рекомендации аналогичны предыдущим, НО поскольку увеличение количества продуктов коррелирует с ростом оттока, следует пересмотреть ассортимент предлагаемых услуг. Возможно, стоит сфокусироваться на улучшении качества имеющихся продуктов, а не на их количестве.
3 сегмент - мужчины в возрасте до 33 лет с 2 и более продуктами. Таких клиентов в нашей базе 1218, из которых 33% уходят(407 клиентов). Это говорит нам о том, что нам не удаётся заинтересовать молодую аудиторию мужчин, как и в первом сегменте, рекомендую пересмотреть политику маркетинговых акций для этого сегмента для их удержания, изучить их основные траты и предложить более выгодные условия.
4 сегмент - мужчины и женщины до 35 лет, включительно, с 2 и более продуктами. На мой взгляд очень перспективный сегмент клиентов, ведь это молодые люди, которые уже готовы использовать несколько продуктов банка, но мы видим, что среди них отточность более чем в 1.5 раза превышает среднюю отточность клиентов, а значит, что они не получают какие-то "плюшки" за выбор банка, которые могут предложить другие банки, возможно, что их интересует бесплатное обслуживание, прежположу, что можно рассмотреть сегменты: beauty, Авто, Досуг(Кафе, Рестораны, Бары) как зоны для дополнительной маркетинговой активности(кэшбеки, акции, доп. предложения за оплату именно картой "МетанПромБанка")
И, наконец, 5 сегмент - мужчины старше 50 лет, таких 10% в базе и из них отточность почти 30%, т.е. 3% всей нашей базы уходят именно в этом сегменте. Вероятно, таких клиентов могут заинтересовать акции и предложения по авто, магазаинам, досугу, покупке товаров для дома, а также возможности для инвестии своих накоплений и/или выгодные условия для вкладов и т.д.
Подытог по двум блокам: Следует внимательнее проанализировать и, возможно, пересмотреть продуктовую линейку банка, уделить внимание более детальной сегментации клиентов по скорингу, обратить внимание на предложения для молодых клиентов и клиентов с большим балансом на счетах банка.
Проверка гипотез:
Первая гипотеза один у нас предполагала, что есть значимая разница в доходах между теми клиентами, что остались и теми, что уходят. Гипотеза опровергнута - значимой разницы мы не обнаружили.
Второй гипотезой мы предположили, что баланс между уходящими и остающимися клиентами не различается значимо, но это теория опровергнута, а равно мы подтвердили свои наблюдения из блока EDA, где увидели, что клиенты с бОльшими балансами склонны к оттоку.